﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ShortcutApl.cs" company="TechBits">
//   Copyright (c) TechBits. All rights reserved.
// </copyright>
// <author>
//   Leandro G. Vacirca.
// </author>
// <summary>
//   Defines the ShortcutApl type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace IntelliShortCut.Application
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using System.Windows.Forms;
    
    using Core;
    using Data;
    using IntelliShortCut.Helpers;
    using log4net;
    using Properties;
    using UI.Forms;

    /// <summary>
    /// Application layer.
    /// </summary>
    public class ShortcutApl
    {
        #region "Private Members"

        /// <summary>
        /// Retrieve a single instance of the class.
        /// </summary>
        private static readonly ShortcutApl _apl = new ShortcutApl();

        /// <summary>
        /// Logger interface.
        /// </summary>
        private readonly ILog _logger;

        /// <summary>
        /// The timer that will backup the shortcuts data periodically.
        /// </summary>
        private readonly System.Windows.Forms.Timer _timerBackup;

        /// <summary>
        /// Circular list of the autocompletion modes.
        /// </summary>
        private readonly List<AutoCompletionModeEnum> _autoCompletionModes;

        /// <summary>
        /// Current autocompletion mode.
        /// </summary>
        private AutoCompletionModeEnum _autoCompletionMode;

        #endregion

        #region "Constructors"

        /// <summary>
        /// Prevents a default instance of the <see cref="ShortcutApl"/> class from being created.
        /// </summary>
        private ShortcutApl()
        {
            // Get access to the logger
            _logger = LogManager.GetLogger(typeof(ShortcutApl));

            // Fill the autocompletion modes
            _autoCompletionModes = new List<AutoCompletionModeEnum>
                                       {
                                           AutoCompletionModeEnum.All,
                                           AutoCompletionModeEnum.Files,
                                           AutoCompletionModeEnum.Folders,
                                           AutoCompletionModeEnum.Browser,
                                           AutoCompletionModeEnum.Special,
                                           AutoCompletionModeEnum.Web
                                       };            

            // Install the windows explorer command context menu for FileName Entry Mode
            foreach (var classRootType in ClassRootType.GetAll())
            {
                var contextMenu = new ShellContextMenu(classRootType.Path, "AddMe to IntelliShortCut", ApplicationConfiguration.FullPath, ((int) ShortcutEnterModeEnum.FileName).ToString());
                contextMenu.Install();

                // Install the windows explorer command context menu for Alias Entry Mode
                contextMenu = new ShellContextMenu(classRootType.Path, "AliasMe to IntelliShortCut", ApplicationConfiguration.FullPath, ((int) ShortcutEnterModeEnum.AliasName).ToString());
                contextMenu.Install();
            }

            // Get access to the data access layer
            ShortcutDal.Instance.OnShorcutsChanged += OnShorcutsChangedHandler;

            _timerBackup = new System.Windows.Forms.Timer { Interval = ApplicationConfiguration.Settings.BackUpPeriod };
            _timerBackup.Tick += TimerBackupTick;
            _timerBackup.Start();
        }

        #endregion

        #region "Events"

        /// <summary>
        /// The shortcuts file has been changed.
        /// </summary>
        public event EventHandler<ShortcutEventArgs> OnShorcutsChanged;

        /// <summary>
        /// The autocompletion mode has been changed.
        /// </summary>
        public event EventHandler<ShortcutEventArgs> OnAutoCompletionModeChanged;

        #endregion

        #region "Propiedades Publicas"

        /// <summary>
        /// Gets a single instance of the class.
        /// </summary>
        /// <returns>
        /// Single instance of the class.
        /// </returns>
        public static ShortcutApl Instance
        {
            get
            {
                return _apl;
            }
        }

        /// <summary>
        /// Gets AutoCompletionMode.
        /// </summary>
        public AutoCompletionModeEnum AutoCompletionMode
        {
            get
            {
                return _autoCompletionMode;                
            } 

            private set
            {
                PreviousAutoCompletionMode = _autoCompletionMode;
                _autoCompletionMode = value;
                OnAutoCompletionModeChangedHandler(new ShortcutEventArgs { AutoCompletionMode = AutoCompletionMode });
            }
        }

        /// <summary>
        /// Gets or sets PreviousAutoCompletionMode.
        /// </summary>
        public AutoCompletionModeEnum PreviousAutoCompletionMode { get; set; }

        /// <summary>
        /// Gets ActiveModeMessage.
        /// </summary>
        public string ActiveModeMessage
        {
            get
            {
                return string.Format("{0} mode activated", Instance.AutoCompletionMode);
            }
        }

        #endregion

        #region "Public Methods"

        /// <summary>
        /// Processes the arguments.
        /// </summary>
        /// <param name="args">The arguments.</param>
        public static void ProcessArguments(string[] args)
        {
            var logger = LogManager.GetLogger(typeof(ShortcutApl));

            try
            {
                if (args.Length <= 0)
                {
                    return;
                }
                            
                // Prepares the shortcut to be added
                var shortcut = new ShortcutData
                                   {
                                       FullPath = args[0],
                                       Icon = ExtractShortcutIcon(args[0]),
                                       Category = GetShortcutCategory(args[0]),
                                       Active = true,
                                   };

                // Get the entry mode
                var shortcutEntryMode = (ShortcutEnterModeEnum)Convert.ToInt32(args[1]);

                // Alias Entry Mode
                if (shortcutEntryMode == ShortcutEnterModeEnum.AliasName)
                {
                    var aliasDialog = new AliasEnterModeForm();                    
                    shortcut.Alias = aliasDialog.ShowForm();

                    // Checks if the user has entered any alias
                    if (String.IsNullOrEmpty(shortcut.Alias))
                    {
                        logger.Debug("The user did not enter any alias.");
                        return;
                    }

                    shortcut.Name = string.Empty;
                    shortcut.ByNameOrAlias = false;
                }
                else
                {
                    // FileName Entry Mode
                    shortcut.Alias = string.Empty;
                    shortcut.Name = Path.GetFileNameWithoutExtension(shortcut.FullPath);
                    shortcut.ByNameOrAlias = true;
                }

                // Add the new shortcut
                ShortcutDal.Instance.Add(shortcut, true);

                logger.DebugFormat("Arguments processed. Details: {0} - {1}", args[0], args[1]);
                logger.InfoFormat("New shortcut added from shell: {0}", shortcut);
            }
            catch (Exception ex)
            {
                logger.ErrorFormat("An error ocurred while processing the arguments. \nDetails: {0}", ex);
            }
        }

        /// <summary>
        /// Adds a new shortcut.
        /// </summary>
        /// <param name="shortcut">The shortcut.</param>
        public void AddShortcut(ShortcutData shortcut)
        {
            AddShortcut(shortcut, false);      
        }

        /// <summary>
        /// Adds a new shortcut.
        /// </summary>
        /// <param name="shortcut">The shortcut.</param>
        /// <param name="extractIcon">The extract Icon.</param>
        public void AddShortcut(ShortcutData shortcut, bool extractIcon)
        {
            try
            {
                if (extractIcon)
                {
                    shortcut.Icon = ExtractShortcutIcon(shortcut.FullPath);
                }

                ShortcutDal.Instance.Add(shortcut);
                _logger.DebugFormat("New shortcut added: {0}", shortcut);
            }
            catch (ApplicationException ex)
            {
                _logger.ErrorFormat("{0} \nDetails: {1}", ex.Message, shortcut);
            }
        }

        /// <summary>
        /// Removes a shortcut.
        /// </summary>
        /// <param name="shortcut">The shortcut.</param>
        public void RemoveShortcut(ShortcutData shortcut)
        {
            try
            {
                ShortcutDal.Instance.Remove(shortcut);
                _logger.DebugFormat("Shortcut removed: {0}", shortcut);
            }
            catch (ApplicationException ex)
            {
                _logger.ErrorFormat("{0} \nDetails: {1}", ex.Message, shortcut);
            }
        }

        /// <summary>
        /// Updates a shortcut.
        /// </summary>
        /// <param name="shortcut">The shortcut.</param>
        public void UpdateShortcut(ShortcutData shortcut)
        {
            try
            {
                ShortcutDal.Instance.Update(shortcut);
                _logger.DebugFormat("Shortcut updated: {0}", shortcut);
            }
            catch (ApplicationException ex)
            {
                _logger.ErrorFormat("{0} \nDetails: {1}", ex.Message, shortcut);
            }  
        }

        /// <summary>
        /// Updates a group of shortcuts to the xml file list.
        /// </summary>
        /// <param name="shortcuts">The shortcuts.</param>
        public void UpdateShortcuts(List<ShortcutData> shortcuts)
        {
            try
            {
                ShortcutDal.Instance.Update(shortcuts);
                _logger.DebugFormat("Shortcuts updated.");
            }
            catch (ApplicationException ex)
            {
                _logger.ErrorFormat("{0} \nDetails: {1}", ex.Message, shortcuts);
            } 
        }

        /// <summary>
        /// Get all shortcuts.
        /// </summary>
        /// <returns>
        /// A list of all shortcuts.
        /// </returns>
        public List<ShortcutData> GetShortcuts()
        {
            return ShortcutDal.Instance.GetAll();
        }

        /// <summary>
        /// Gets all shortcuts of an specific active state.
        /// </summary>
        /// <param name="active">Indicates if the shortcut is active or not.</param>
        /// <returns>
        /// A list with shortcuts of an specific active state.
        /// </returns>
        public List<ShortcutData> GetShortcuts(bool active)
        {
            return ShortcutDal.Instance.GetAll(active);
        }

        /// <summary>
        /// Gets all shortcuts of an specific active and valid state.
        /// </summary>
        /// <param name="active">Indicates if the shortcut is active or not.</param>
        /// <param name="valid">Indicates if the sohrtcut is valid or not.</param>
        /// <returns>
        /// A list with shortcuts of an specific active and valid state.
        /// </returns>
        public List<ShortcutData> GetShortcuts(bool active, bool valid)
        {
            return GetShortcuts(active).Where(s => s.Valid.Equals(valid)).Select(s => s).ToList();
        }

        /// <summary>
        /// Gets the shortcut icon.
        /// </summary>
        /// <param name="keyword">The name or alias of the shortcut.</param>
        /// <returns>
        /// The shortcut icon.
        /// </returns>
        public Bitmap GetShortcutIcon(string keyword)
        {
            try
            {
                // Return the default icon
                if (string.IsNullOrEmpty(keyword))
                {
                    return Resources.IS;
                }

                // Return the special commands icon
                if (AutoCompletionMode.Equals(AutoCompletionModeEnum.Special))
                {
                    return Resources.SpecialCommands.ToBitmap();
                }

                // Checks if the user if trying to open a web page
                if (keyword.StartsWith("w:"))
                {
                    return Resources.WebMode;
                }

                // Checks if the user wants to make a google search
                if (keyword.StartsWith("g:"))
                {
                    return Resources.Google;
                }

                // Checks if the shortcuts is on list mode
                if (keyword.StartsWith("."))
                {
                    keyword = keyword.Substring(4);
                }

                // Return the icon associated to the name or alias
                var shortcut = ShortcutDal.Instance
                    .GetAll(true, GetCategoryFromMode(AutoCompletionMode))
                    .Single(s => s.Name.ToUpper() == keyword.ToUpper() || s.Alias.ToUpper() == keyword.ToUpper());

                return shortcut.Icon;
            }
            catch (Exception)
            {
                return Resources.IS;
            }
        }

        /// <summary>
        /// Extracts the icon associated to the shortcut.
        /// </summary>
        /// <param name="fullPath">The full path.</param>
        /// <returns>
        /// The icon associated to the shortcut.
        /// </returns>
        public static Bitmap ExtractShortcutIcon(string fullPath)
        {
            Bitmap icon;
            if (!Path.HasExtension(fullPath))
            {
                // The shortcut is associated to a folder
                icon = Resources.Folder.ToBitmap();
            }
            else if (Path.GetExtension(fullPath) == ".msc")
            {
                icon = Resources.MscFiles.ToBitmap();
            }
            else
            {
                try
                {
                    using (var iconExtractor = new IconExtractor(fullPath))
                    {
                        icon = iconExtractor.GetIcon(0).ToBitmap();
                    }
                }
                catch (Exception)
                {
                    icon = Resources.MscFiles.ToBitmap();
                }
            }

            return icon;
        }

        /// <summary>
        /// Gets the autocompletion mode on a circular way.
        /// </summary>
        /// <returns>
        /// The auto completion mode.
        /// </returns>
        public string GetAutoCompletionMode()
        {
            int nextIndex;
            var curIndex = _autoCompletionModes.IndexOf(AutoCompletionMode);
            if (curIndex == _autoCompletionModes.Count - 1)
            {
                nextIndex = 0;
            }
            else
            {
                nextIndex = curIndex + 1;
            }

            return string.Concat(".", _autoCompletionModes[nextIndex].ToString());
        }

        /// <summary>
        /// Sets the autocompletion mode.
        /// </summary>
        /// <param name="autoCompletionMode">The auto completion mode.</param>
        public void SetAutoCompletionMode(AutoCompletionModeEnum autoCompletionMode)
        {
            AutoCompletionMode = autoCompletionMode;
        }

        /// <summary>
        /// Sets the autocompletion mode from a keyword.
        /// </summary>
        /// <param name="keyword">The keyword.</param>
        /// <returns>
        /// True: if the autocompletion mode could be defined; False: otherwise.
        /// </returns>
        public bool SetAutoCompletionMode(string keyword)
        {
            try
            {
                var modeFromKeyword = ShortcutCommands.GetModeFromKeyword(keyword);
                if (modeFromKeyword != null)
                {
                    AutoCompletionMode = modeFromKeyword.Value;
                    return true;
                }
            }
            catch
            {
                return false;
            }

            return false;
        }

        /// <summary>
        /// Fill the autocompletion list of shortcuts for the current autocompletion mode.
        /// </summary>
        /// <returns>
        /// Autocompletion list of shortcuts for the current autocompletion mode.
        /// </returns>
        public AutoCompleteStringCollection GetAutoCompletionList()
        {
            var autoCompleteList = new AutoCompleteStringCollection();

            try
            {
                var mode = AutoCompletionMode.Equals(AutoCompletionModeEnum.List)
                               ? PreviousAutoCompletionMode
                               : AutoCompletionMode;

                switch (mode)
                {
                    case AutoCompletionModeEnum.Last:
                    {
                        ShortcutDal.Instance.GetAll(true, true).ForEach(s => autoCompleteList.Add(s.ByNameOrAlias ? s.Name : s.Alias));                            
                        break;
                    }

                    case AutoCompletionModeEnum.Most:
                    {
                        ShortcutDal.Instance.GetAll(true, false).ForEach(s => autoCompleteList.Add(s.ByNameOrAlias ? s.Name : s.Alias));                            
                        break;                            
                    }

                    case AutoCompletionModeEnum.Special:
                    {
                        var specialFolders = Enum.GetValues(typeof(Environment.SpecialFolder));
                        foreach (var folder in specialFolders)
                        {
                            autoCompleteList.Add(folder.ToString());
                        }

                        break;                            
                    }

                    case AutoCompletionModeEnum.All:
                    case AutoCompletionModeEnum.Files:
                    case AutoCompletionModeEnum.Folders:
                    case AutoCompletionModeEnum.Browser:
                    case AutoCompletionModeEnum.Web:
                    {
                        var shortcuts = ShortcutDal.Instance.GetAll(true, GetCategoryFromMode(mode))
                            .OrderByDescending(s => s.Rank)
                            .ToList();                                                    
                        shortcuts.ForEach(s => autoCompleteList.Add(s.ByNameOrAlias ? s.Name : s.Alias));
                        
                        break;
                    }
                }

                // Makes a list of shortcuts sorted by rank
                if (AutoCompletionMode.Equals(AutoCompletionModeEnum.List))
                {                    
                    for (int i = 0; i < autoCompleteList.Count; i++)
                    {
                        var rank = string.Concat(".", string.Format("{0} ", i).PadLeft(3, '0'));
                        autoCompleteList[i] = string.Concat(rank, autoCompleteList[i]);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex);
            }

            return autoCompleteList;
        }

        /// <summary>
        /// Executes an application associated to the shortcut.
        /// </summary>
        /// <param name="keyword">The shortcut name.</param>
        public void ExecuteShortcut(string keyword)
        {
            try
            {
                // Checks if the user wants to make a google search
                if (keyword.StartsWith("g:"))
                {
                    var searchQuery = keyword.Substring(keyword.IndexOf(":") + 1);
                    ShortcutExecutor.Execute(string.Format("http://www.google.com.ar/search?hl=es&q={0}", searchQuery));

                    return;
                }

                // Checks if the user wants to open a web page
                if (keyword.StartsWith("w:"))
                {
                    string url;
                    string ip;
                    if (!keyword.StartsWith("www") || !keyword.StartsWithIP(out ip))
                    {
                        var cleanKeyword = keyword.Replace("w:", string.Empty).Trim();
                        url = !cleanKeyword.StartsWithIP(out ip) ? string.Concat("http://www.", cleanKeyword) : string.Concat("http://", cleanKeyword);
                        ShortcutExecutor.Execute(url);

                        // Adds the url as shortcut
                        AddShortcut(new ShortcutData
                                           {
                                               Active = true,
                                               ByNameOrAlias = true,
                                               Name = url.Replace("http://", string.Empty),
                                               Alias = string.Empty,
                                               Category = ShortcutCategoryEnum.Url,
                                               FullPath = url,
                                               Valid = true,
                                               Icon = Resources.WebMode,
                                               Rank = 1,
                                               LastUsed = DateTime.Now
                                           });                        
                        return;
                    }                              
                }
                
                if (AutoCompletionMode.Equals(AutoCompletionModeEnum.List))
                {
                    // Checks if the shortcuts is on list mode
                    if (keyword.StartsWith("."))
                    {
                        keyword = keyword.Substring(4);
                    }
                }

                var mode = AutoCompletionMode.Equals(AutoCompletionModeEnum.List)
                               ? PreviousAutoCompletionMode
                               : AutoCompletionMode;

                switch (mode)
                {
                    case AutoCompletionModeEnum.Special:
                        {
                            // Checks if the shortcuts is an special command
                            var specialFolder = GetSpecialFolder(keyword);
                            if (specialFolder != null)
                            {
                                ShortcutExecutor.Execute(specialFolder.Value);
                                return;
                            }

                            break;
                        }

                    case AutoCompletionModeEnum.Browser:
                        {
                            ShortcutExecutor.Execute(keyword);
                            return;
                        } 

                    case AutoCompletionModeEnum.All:
                    case AutoCompletionModeEnum.Files:
                    case AutoCompletionModeEnum.Folders:
                    case AutoCompletionModeEnum.Last:
                    case AutoCompletionModeEnum.Most:
                    case AutoCompletionModeEnum.Web:
                        {
                            // All, Files, Folders modes (and list mode)
                            var shortcut = ShortcutDal.Instance.GetAll(true, GetCategoryFromMode(AutoCompletionMode)).Single(
                                s => s.ByNameOrAlias ? s.Name.ToUpper() == keyword.ToUpper() : s.Alias.ToUpper() == keyword.ToUpper());

                            // Execute the shortcut
                            ShortcutExecutor.Execute(shortcut);
                            UpdateShortcutStatistics(shortcut);

                            return;
                        }
                }
            }
            catch (Exception ex)
            {
                _logger.ErrorFormat("An error ocurred while trying to execute the shortcut. \nDetails: {0}", ex);
            }
        }

        /// <summary>
        /// Gets the validation status of the shortcuts.
        /// </summary>
        /// <returns>
        /// True: all shortcuts are valid; False: otherwise.
        /// </returns>
        public bool GetValidationStatus()
        {
            var shortcuts = GetShortcuts();
            return shortcuts.All(s => s.Valid.Equals(true));
        }

        /// <summary>
        /// Commits changes to the xml database.
        /// </summary>
        /// <remarks>
        /// This method must be called before closing the application.
        /// </remarks>
        public void Commit()
        {
            try
            {
                _timerBackup.Stop();

                // Saves the shortcuts count before make a commit
                ApplicationConfiguration.Settings.LastSaveDate = DateTime.Now;
                ApplicationConfiguration.Settings.ShortcutsSavedCount = GetShortcuts().Count();
                ApplicationConfiguration.SaveSettings();

                var commitThread = new Thread(ShortcutDal.Instance.Commit) { IsBackground = false };
                commitThread.Start();
                commitThread.Join(60000); 
            }
            catch (Exception ex)
            {
                _logger.ErrorFormat("An error ocurred while trying to commit the changes to the DB. \nDetails: {0}", ex);
            }
        }

        #endregion

        #region "Private Methods"

        /// <summary>
        /// Makes a backup periodically.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event parameters.</param>
        private void TimerBackupTick(object sender, EventArgs e)
        {
            try
            {
                ShortcutDal.Instance.BackUp();
            }
            catch (Exception ex)
            {
                _logger.ErrorFormat("There was an error while trying to make a backup.\nDetails: {0}", ex);
            }
        }

        /// <summary>
        /// Updates the shortcuts statistics.
        /// </summary>
        /// <param name="shortcut">The shortcut.</param>
        private static void UpdateShortcutStatistics(ShortcutData shortcut)
        {
            shortcut.Rank++;
            shortcut.LastUsed = DateTime.Now;
            ShortcutDal.Instance.Update(shortcut);
        }

        /// <summary>
        /// Gets the special folder from a keyword.
        /// </summary>
        /// <param name="keyword">The keyword.</param>
        /// <returns>
        /// The special folder or null if the keyword is not an special folder.
        /// </returns>
        private static Environment.SpecialFolder? GetSpecialFolder(string keyword)
        {
            try
            {
                // Formats the keyword entered
                keyword = keyword.Replace(".", string.Empty).Trim();
                
                // Checks if the leyword is a valid mode
                var specialFolders = Enum.GetNames(typeof(Environment.SpecialFolder)).ToList();
                if (!specialFolders.Contains(keyword))
                {
                    return null;
                }
            }
            catch
            {
                return null;
            }

            return (Environment.SpecialFolder)Enum.Parse(typeof(Environment.SpecialFolder), keyword);
        }

        /// <summary>
        /// Gets the category from the autocompletion mode.
        /// </summary>
        /// <param name="autoCompletionMode">The autocompletion mode.</param>
        /// <returns>
        /// The shortcut category associated to the autocompletion mode.
        /// </returns>
        private static ShortcutCategoryEnum? GetCategoryFromMode(AutoCompletionModeEnum autoCompletionMode)
        {
            switch (autoCompletionMode)
            {
                case AutoCompletionModeEnum.All:
                    {
                        return null;
                    }

                case AutoCompletionModeEnum.Files:
                    {
                        return ShortcutCategoryEnum.Files;
                    }

                case AutoCompletionModeEnum.Folders:
                    {
                        return ShortcutCategoryEnum.Folders;
                    }

                case AutoCompletionModeEnum.Web:
                    {
                        return ShortcutCategoryEnum.Url;
                    }

                default:
                    return null;
            }
        }

        /// <summary>
        /// Get the category of the shortcut.
        /// </summary>
        /// <param name="fullPath">The full path.</param>
        /// <returns>
        /// The category of the shortcut.
        /// </returns>
        private static ShortcutCategoryEnum GetShortcutCategory(string fullPath)
        {
            return Path.HasExtension(fullPath) ? ShortcutCategoryEnum.Files : ShortcutCategoryEnum.Folders;
        }

        /// <summary>
        /// Invokes the event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event parameters.</param>
        private void OnShorcutsChangedHandler(object sender, ShortcutEventArgs e)
        {
            if (OnShorcutsChanged != null)
            {
                OnShorcutsChanged(this, e);
            }
        }

        /// <summary>
        /// Invokes the event.
        /// </summary>
        /// <param name="e">The event parameters.</param>
        private void OnAutoCompletionModeChangedHandler(ShortcutEventArgs e)
        {
            if (OnAutoCompletionModeChanged != null)
            {
                OnAutoCompletionModeChanged(this, e);
            }
        }

        #endregion
    }
}
